import React, { useState, useCallback, useRef, useMemo } from "react";
import { DndProvider } from 'react-dnd';
import { DragSourceMonitor, DropTargetMonitor, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { XYCoord } from 'dnd-core';
import update from 'immutability-helper';
import './index.css';

const CARD_INIT_ARR = [
    { id: 1, text: 'Apple', bg: 'red' },
    { id: 2, text: 'Banana', bg: 'yellow' },
    { id: 3, text: 'Orange', bg: 'orange' },
    { id: 4, text: 'Grape', bg: 'purple' },
    { id: 5, text: 'Watermelon', bg: 'green' },
    { id: 6, text: 'Peach', bg: 'pink' },
];
enum ItemTypes {
    Card = 'card'
}

let id = 1;

const Box = ({ bg, text, dropCardList, updateDragAndDrop }: any) => {
    const style: React.CSSProperties = {
        background: bg,
    };

    const box = {
        bg,
        text,
    };

    const [{ isDragging }, drag] = useDrag({
        type: ItemTypes.Card,
        item() {
            //在拖动操作开始时触发
            const useless = dropCardList.find((item: any) => item.id === -1);
            // 拖拽开始时，向 cardList 数据源中插入一个占位的元素，如果占位元素已经存在，不再重复插入
            if (!useless) {
                updateDragAndDrop([{ bg: 'aqua', text: '放这里', id: -1 }, ...dropCardList]);
            }

            return box;
        },
        end(_: unknown, monitor: DragSourceMonitor) {
            const uselessIndex = dropCardList.findIndex((item: any) => item.id === -1);

            /**
             * 拖拽结束时，判断是否将拖拽元素放入了目标接收组件中
             *  1、如果是，则使用真正传入的 box 元素代替占位元素
             *  2、如果否，则将占位元素删除
             */

            if (monitor.didDrop()) {
                dropCardList.splice(uselessIndex, 1, { ...monitor.getItem(), id: id++ });
            } else {
                dropCardList.splice(uselessIndex, 1);
            }
            // 更新 cardList 数据源
            updateDragAndDrop(dropCardList);
        },
        collect: (monitor) => ({
            isDragging: monitor.isDragging(),
        }),
    });
    return (
        <div ref={drag} style={style} className={'card_drag'}>
            {text}
        </div>
    );
};

const Card = ({ bg, text, index, moveCard, id, updateDragAndDrop, dropCardList }: any) => {
    const ref = useRef<HTMLDivElement>(null);

    const [{ isDragging }, drag] = useDrag({
        type: ItemTypes.Card,
        collect: (monitor: any) => ({
            isDragging: monitor.getItem() ? index === monitor.getItem().index : false, // 直接用monitor.isDragging无法监控索引更改
            // isDragging: monitor.isDragging()
        }),
        // item 中包含 index 属性，则在 drop 组件 hover 和 drop 是可以根据第一个参数获取到 index 值
        item: { index, id },

        end(item: any, monitor: DragSourceMonitor) {
            const idx = item.id;
            const uselessIndex = dropCardList.findIndex((item: any) => item.id === idx);

            /**
             * 监控是否将元素移出了列表
             */
            if (!monitor.didDrop()) {
                dropCardList.splice(uselessIndex, 1);
            }
            // 更新 cardList 数据源
            updateDragAndDrop(dropCardList);
        },
    });

    const [, drop] = useDrop({
        accept: ItemTypes.Card,
        hover(item: any, monitor: DropTargetMonitor) {
            if (!ref.current) {
                return;
            }
            const dragIndex = item.index;
            const hoverIndex = index;

            // 拖拽元素下标与鼠标悬浮元素下标一致时，不进行操作
            if (dragIndex === hoverIndex) {
                return;
            }

            // 确定屏幕上矩形范围
            const hoverBoundingRect = ref.current!.getBoundingClientRect();

            // 获取中点垂直坐标
            const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

            // 确定鼠标位置
            const clientOffset = monitor.getClientOffset();

            // 获取距顶部距离
            const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

            /**
             * 只在鼠标越过一半物品高度时执行移动。
             *
             * 当向下拖动时，仅当光标低于50%时才移动。
             * 当向上拖动时，仅当光标在50%以上时才移动。
             *
             * 可以防止鼠标位于元素一半高度时元素抖动的状况
             */

            // 向下拖动
            if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
                return;
            }

            // 向上拖动
            if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
                return;
            }

            // 执行 move 回调函数
            moveCard(dragIndex, hoverIndex);

            /**
             * 如果拖拽的组件为 Box，则 dragIndex 为 undefined，此时不对 item 的 index 进行修改
             * 如果拖拽的组件为 Card，则将 hoverIndex 赋值给 item 的 index 属性
             */
            if (item.index !== undefined) {
                item.index = hoverIndex;
            }
        },
    });

    const style: React.CSSProperties = useMemo(
        () => ({
            // Card 为占位元素是，透明度 0.4，拖拽状态时透明度 0.2，正常情况透明度为 1
            opacity: id === -1 ? 0.4 : isDragging ? 1 : 1,
            backgroundColor: bg,
        }),
        [bg, id, isDragging],
    );

    /**
     * 使用 drag 和 drop 对 ref 进行包裹，则组件既可以进行拖拽也可以接收拖拽组件
     */
    return (
        <div ref={drag(drop(ref)) as any} style={style} className='card_drag'>
            {text}
        </div>
    );
};

const DragSquare = ({ dragCardList, dropCardList, updateDragAndDrop }: any) => {
    return (
        <div className='card_drag_group'>
            {dragCardList.map((each: any, index: any) => (
                <Box
                    index={index}
                    {...each}
                    key={'drag_card' + index}
                    dropCardList={dropCardList}
                    updateDragAndDrop={updateDragAndDrop}
                />
            ))}
        </div>
    )
}

function DropSquare({ dropCardList, updateDragAndDrop }: any) {
    const [{ canDrop }, drop] = useDrop({
        accept: ItemTypes.Card,
        canDrop: (_item, monitor) => {
            return true;
        },
        collect: (monitor) => ({
            canDrop: !!monitor.canDrop(),
        }),
    });

    const moveCard = useCallback(
        (dragIndex: number, hoverIndex: number) => {
            console.log('dragIndex', dragIndex, 'hoverIndex', hoverIndex);

            /**
             * 1、如果此时拖拽的组件是 Box 组件，则 dragIndex 为 undefined，则此时修改，则此时修改 cardList 中的占位元素的位置即可
             * 2、如果此时拖拽的组件是 Card 组件，则 dragIndex 不为 undefined，此时替换 dragIndex 和 hoverIndex 位置的元素即可
             */
            if (dragIndex === undefined) {
                const lessIndex = dropCardList.findIndex((item: any) => item.id === -1);
                updateDragAndDrop(
                    update(dropCardList, {
                        $splice: [
                            [lessIndex, 1],
                            [hoverIndex, 0, { bg: 'aqua', category: '放这里', id: -1 }],
                        ],
                    }),
                );
            } else {
                const dragCard = dropCardList[dragIndex];
                updateDragAndDrop(
                    update(dropCardList, {
                        $splice: [
                            [dragIndex, 1],
                            [hoverIndex, 0, dragCard],
                        ],
                    }),
                );
            }
            // eslint-disable-next-line
        },
        [dropCardList, updateDragAndDrop],
    );

    return (
        <div className='card_drop_group' ref={drop}>
            {dropCardList.length > 0 &&
                dropCardList.map((each: any, index: number) => (
                    <Card
                        key={'drop_place' + index}
                        {...each}
                        index={index}
                        dropCardList={dropCardList}
                        updateDragAndDrop={updateDragAndDrop}
                        moveCard={moveCard}
                    />
                ))}
        </div>
    );
}

function CopyListSort() {
    const [dragCardList, setDragCardList] = useState<any>(CARD_INIT_ARR);
    const [dropCardList, setDropCardList] = useState<any>([]);
    const updateDragAndDrop = (newCardList: any) => {
        console.log("newCardList-before", newCardList);
        setDropCardList((dropCardList: any) => [...newCardList]);
    }
    return (
        <DndProvider backend={HTML5Backend}>
            <div className='scard-move-container'>
                <h2>列表排序(数量无限，有顺移)</h2>
                <DropSquare
                    dragCardList={dragCardList}
                    dropCardList={dropCardList}
                    updateDragAndDrop={updateDragAndDrop}
                />
                <DragSquare
                    dropCardList={dropCardList}
                    dragCardList={dragCardList}
                    updateDragAndDrop={updateDragAndDrop} />
            </div>
        </DndProvider>
    );
}

export default CopyListSort;
